package org.zalando.riptide.stream; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageNotReadableException; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_XML; import static org.zalando.riptide.stream.Streams.APPLICATION_JSON_SEQ; import static org.zalando.riptide.stream.Streams.APPLICATION_X_JSON_STREAM; public class StreamConverterTest { @Rule public final ExpectedException exception = ExpectedException.none(); @Test public void shouldSupportMediaTypes() throws Exception { final List<MediaType> medias = new StreamConverter<>().getSupportedMediaTypes(); assertThat(medias, hasItem(APPLICATION_X_JSON_STREAM)); assertThat(medias, hasItem(APPLICATION_JSON_SEQ)); } @Test public void shouldSupportRead() throws Exception { final ObjectMapper mapper = mock(ObjectMapper.class); final TypeFactory factory = new ObjectMapper().getTypeFactory(); when(mapper.getTypeFactory()).thenReturn(factory); when(mapper.canDeserialize(any())).thenReturn(true); final StreamConverter<?> unit = new StreamConverter<>(mapper, singletonList(APPLICATION_JSON)); assertFalse(unit.canRead(Object.class, APPLICATION_XML)); assertFalse(unit.canRead(Stream.class, APPLICATION_JSON)); assertTrue(unit.canRead(Object.class, APPLICATION_JSON)); assertTrue(unit.canRead(List.class, APPLICATION_JSON)); assertTrue(unit.canRead(List[].class, APPLICATION_JSON)); assertTrue(unit.canRead(AccountBody.class, null)); assertTrue(unit.canRead(AccountBody[].class, null)); when(mapper.canDeserialize(factory.constructType(AccountBody.class))).thenReturn(false); assertFalse(unit.canRead(AccountBody.class, APPLICATION_JSON)); } @Test public void shouldSupportReadGeneric() throws Exception { final ObjectMapper mapper = mock(ObjectMapper.class); final TypeFactory factory = new ObjectMapper().getTypeFactory(); when(mapper.getTypeFactory()).thenReturn(factory); when(mapper.canDeserialize(any())).thenReturn(true); final StreamConverter<?> unit = new StreamConverter<>(mapper, singletonList(APPLICATION_X_JSON_STREAM)); assertFalse(unit.canRead(Streams.streamOf(Object.class).getType(), getClass(), APPLICATION_XML)); assertTrue(unit.canRead(Streams.streamOf(List.class).getType(), getClass(), APPLICATION_X_JSON_STREAM)); assertTrue(unit.canRead(Streams.streamOf(List[].class).getType(), getClass(), APPLICATION_X_JSON_STREAM)); assertTrue(unit.canRead(Streams.streamOf(AccountBody.class).getType(), getClass(), null)); assertTrue(unit.canRead(Streams.streamOf(AccountBody[].class).getType(), getClass(), null)); when(mapper.canDeserialize(factory.constructType(AccountBody.class))).thenReturn(false); assertFalse(unit.canRead(Streams.streamOf(AccountBody.class).getType(), getClass(), APPLICATION_X_JSON_STREAM)); } @Test public void shouldNotSupportWrite() throws Exception { final StreamConverter<?> unit = new StreamConverter<>(); assertFalse(unit.canWrite(AccountBody.class, APPLICATION_X_JSON_STREAM)); } @Test public void shouldNotSupportWriteGeneric() throws Exception { final StreamConverter<?> unit = new StreamConverter<>(); assertFalse(unit.canWrite(Streams.streamOf(AccountBody.class).getType(), null, APPLICATION_X_JSON_STREAM)); } private HttpInputMessage mockWithContentType(final MediaType mediaType) { final HttpInputMessage input = mock(HttpInputMessage.class); final HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); when(input.getHeaders()).thenReturn(headers); return input; } @Test public void shouldSupportReadItem() throws Exception { final StreamConverter<AccountBody> unit = new StreamConverter<>(new ObjectMapper().findAndRegisterModules(), singletonList(APPLICATION_JSON)); final HttpInputMessage input = mockWithContentType(APPLICATION_JSON); when(input.getBody()).thenReturn(new ClassPathResource("account-item.json").getInputStream()); assertThat(unit.read(AccountBody.class, input), is(new AccountBody("1234567890", "Acme Corporation"))); } @Test public void shouldSupportReadStream() throws Exception { final Type type = Streams.streamOf(AccountBody.class).getType(); final StreamConverter<Stream<AccountBody>> unit = new StreamConverter<>(new ObjectMapper().findAndRegisterModules(), singletonList(APPLICATION_X_JSON_STREAM)); final HttpInputMessage input = mockWithContentType(APPLICATION_X_JSON_STREAM); when(input.getBody()).thenReturn(new ClassPathResource("account-stream.json").getInputStream()); final Stream<AccountBody> stream = unit.read(type, null, input); @SuppressWarnings("unchecked") final Consumer<? super AccountBody> verifier = mock(Consumer.class); stream.forEach(verifier); verify(verifier).accept(new AccountBody("1234567890", "Acme Corporation")); verify(verifier).accept(new AccountBody("1234567891", "Acme Company")); verify(verifier).accept(new AccountBody("1234567892", "Acme GmbH")); verify(verifier).accept(new AccountBody("1234567893", "Acme SE")); verify(verifier, times(4)).accept(any(AccountBody.class)); } @Test public void shouldSupportReadSequence() throws Exception { final Type type = Streams.streamOf(AccountBody.class).getType(); final StreamConverter<Stream<AccountBody>> unit = new StreamConverter<>(new ObjectMapper().findAndRegisterModules(), singletonList(APPLICATION_JSON_SEQ)); final HttpInputMessage input = mockWithContentType(APPLICATION_JSON_SEQ); when(input.getBody()).thenReturn(new ClassPathResource("account-sequence.json").getInputStream()); final Stream<AccountBody> stream = unit.read(type, null, input); @SuppressWarnings("unchecked") final Consumer<? super AccountBody> verifier = mock(Consumer.class); stream.forEach(verifier); verify(verifier).accept(new AccountBody("1234567890", "Acme Corporation")); verify(verifier).accept(new AccountBody("1234567891", "Acme Company")); verify(verifier).accept(new AccountBody("1234567892", "Acme GmbH")); verify(verifier).accept(new AccountBody("1234567893", "Acme SE")); verify(verifier, times(4)).accept(any(AccountBody.class)); } @Test public void shouldWrapIOExceptionOnClose() throws Exception { exception.expect(UncheckedIOException.class); exception.expectCause(instanceOf(IOException.class)); final ObjectMapper mapper = spy(new ObjectMapper().findAndRegisterModules()); final JsonFactory factory = spy(mapper.getFactory()); final JsonParser parser = mock(JsonParser.class); when(mapper.getFactory()).thenReturn(factory); when(factory.createParser(any(InputStream.class))).thenReturn(parser); doThrow(new IOException()).when(parser).close(); final Type type = Streams.streamOf(AccountBody.class).getType(); final StreamConverter<Stream<AccountBody>> unit = new StreamConverter<>(mapper,null); final HttpInputMessage input = mockWithContentType(APPLICATION_X_JSON_STREAM); when(input.getBody()).thenReturn(new ClassPathResource("account-stream.json").getInputStream()); try (Stream<AccountBody> stream = unit.read(type, null, input)) { // nothing to do. stream.close(); } } @Test public void shouldFailOnReadForIOException() throws Exception { exception.expect(HttpMessageNotReadableException.class); exception.expectCause(instanceOf(IOException.class)); final StreamConverter<?> unit = new StreamConverter<>(); final HttpInputMessage input = mockWithContentType(APPLICATION_X_JSON_STREAM); doThrow(new IOException()).when(input).getBody(); unit.read(Streams.streamOf(Object.class).getType(), null, input); } @Test public void writeNotSupported() throws Exception { exception.expect(UnsupportedOperationException.class); final StreamConverter<?> unit = new StreamConverter<>(); unit.write(null, APPLICATION_X_JSON_STREAM, null); } @Test public void writeGenericNotSupported() throws Exception { exception.expect(UnsupportedOperationException.class); final StreamConverter<?> unit = new StreamConverter<>(); unit.write(null, Streams.streamOf(AccountBody.class).getType(), APPLICATION_X_JSON_STREAM, null); } }